package cn.demoncat.util.redis.aop;

import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.Result;
import cn.demoncat.util.redis.annotation.Lock;
import cn.demoncat.util.redis.entity.RedisLock;
import cn.demoncat.util.redis.util.RedisLockUtil;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 分布式锁 - 注解式
 *
 * 对@Lock注解的方法进行加锁处理，tryLock锁获取失败时返回{402:服务繁忙}
 *
 * @author 延晓磊
 * @since 2021年02月13日
 */
@Aspect
@Order(-900)
@Component
public class LockAspect {

    @Resource
    private RedisLockUtil redisLockUtil;

    /**
     * 声明切点：@Lock注解
     *
     * @author 延晓磊
     * @since 2021年02月13日
     */
    @Pointcut("@annotation(cn.demoncat.util.redis.annotation.Lock)")
    public void checkPoint() { }

    /**
     * 环绕通知
     *
     * 1、进入方法前加锁
     * 2、未获取锁时：返回值为Result就返回{402:服务繁忙}，其它返回null
     *
     * @param point
     * @return
     *
     * @author 延晓磊
     *
     * @since 2021年02月13日
     */
    @Around("checkPoint()")
    public Object around(ProceedingJoinPoint point){
        // 获取方法签名对象
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 获取方法对象
        Method method = signature.getMethod();
        // 获取方法的注解
        Lock ant = method.getAnnotation(Lock.class);
        // 转换为锁对象
        RedisLock lock = new RedisLock(ant.key());
        if (ArrayUtils.isNotEmpty(ant.keyArg())) {
            // 缓存键 拼接 方法参数
            if (ant.keyArg().length == 1) {
                // 单参：缓存键 + 方法参数[keyArg[0]]
                lock.setKey(lock.key(point.getArgs()[ant.keyArg()[0]]));
            }else{
                // 多参：缓存键 + 方法参数[keyArg[1]] + : + 方法参数[keyArg[1]]
                List<String> argList = new ArrayList<>(ant.keyArg().length);
                String arg;
                for (int i : ant.keyArg()) {
                    arg = StringUtil.toString(point.getArgs()[ant.keyArg()[i]]);
                    if (StringUtils.isNotBlank(arg)) {
                        argList.add(arg);
                    }
                }
                if (!argList.isEmpty()) {
                    lock.setKey(lock.key(StringUtils.join(argList, StringConstant.COLON)));
                }
            }
        }
        lock.setExp(ant.exp()).setLevel(ant.level()).setTx(ant.tx()).setTryLock(ant.tryLock()).setTryTime(ant.tryTime());
        // 加锁
        return redisLockUtil.lock(lock, ()-> {
            try {
                // 业务操作
                return point.proceed();
            } catch (Throwable tr) {
                // 执行失败
                throw new RuntimeException(tr);
            }
            // 加锁失败
        }, () -> Result.class.equals(method.getReturnType()) ? Result.ERROR_BUSY : null);
    }

}
